﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Internal.UIOC;
using UIOC;
using UIOC.Attributes;

namespace UIOC {

    /// <summary>
    /// 类型映射表
    /// </summary>
    public interface ITypeMap {

        /// <summary>
        /// 实例化
        /// </summary>
        /// <typeparam name="TInterface">接口类型</typeparam>
        /// <returns>实现了<typeparamref name="TInterface"/>接口的类型的实例</returns>
        TInterface Instantiate<TInterface>();

        /// <summary>
        /// 实例化
        /// </summary>
        /// <param name="interfaceType">接口类型</param>
        /// <returns>实现了<paramref name="interfaceType"/>接口的类型的实例</returns>
        IObject Instantiate(Type interfaceType);

        /// <summary>
        /// 销毁
        /// </summary>
        /// <param name="instance">实例化对象</param>
        void Dispose(object instance);

        /// <summary>
        /// 获取实现类型
        /// </summary>
        /// <typeparam name="TInterface">接口类型</typeparam>
        /// <returns>实现了<typeparamref name="TInterface"/>接口的类型</returns>
        Type Fetch<TInterface>();

        /// <summary>
        /// 获取实现类型
        /// </summary>
        /// <param name="interfaceType">接口类型</param>
        /// <returns>实现了<paramref name="interfaceType"/>接口的类型</returns>
        Type Fetch(Type interfaceType);
    }

#if !DISABLE_DEBUGGER_ATTRIBUTE
    [DebuggerStepThrough]
    [DebuggerNonUserCode]
#endif
    internal sealed class TypeMap : ITypeMap, ITypeMapInternal {

        public event ObjectTypeMap.BeforeConstructDelegate BeforeConstruct {
            add {
                m_SingletonBeanTypeMap.BeforeConstruct += value;
                m_DefaultBeanTypeMap.BeforeConstruct += value;
            }

            remove {
                m_SingletonBeanTypeMap.BeforeConstruct -= value;
                m_DefaultBeanTypeMap.BeforeConstruct -= value;
            }
        }

        private readonly SingletonBeanTypeMap m_SingletonBeanTypeMap = new();
        private readonly DefaultBeanTypeMap m_DefaultBeanTypeMap = new();

        private readonly IDictionary<Type, InstanceType> m_InstanceTypeDictionary = new Dictionary<Type, InstanceType>();

        public void Clear() {
            m_InstanceTypeDictionary.Clear();
            m_DefaultBeanTypeMap.Clear();
            m_SingletonBeanTypeMap.Clear();
        }

        private IBeanTypeMapInternal GetTypeMap(InstanceType instanceType) {
            return instanceType switch {
                InstanceType.Default => m_DefaultBeanTypeMap,
                InstanceType.Singleton => m_SingletonBeanTypeMap,
                _ => throw new NotImplementedException(),
            };
        }

        public void Register(Type interfaceType, Type beanType, InstanceType instanceType, bool logEnabled = true) {
            var typeMap = GetTypeMap(instanceType);
            typeMap.Register(interfaceType, beanType, instanceType, logEnabled);
            m_InstanceTypeDictionary[interfaceType] = instanceType;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Type Fetch<TInterface>() => Fetch(typeof(TInterface));
        public Type Fetch(Type interfaceType) {
            if (m_InstanceTypeDictionary.TryGetValue(interfaceType, out var instanceType)) {
                var typeMap = GetTypeMap(instanceType);
                return typeMap.Fetch(interfaceType);
            }
            var record = m_SingletonBeanTypeMap.FetchRecord(interfaceType);
            record ??= m_DefaultBeanTypeMap.FetchRecord(interfaceType);
            m_InstanceTypeDictionary.Add(interfaceType, record.InstanceType);
            return record.ClassType;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public TInterface Instantiate<TInterface>() => (TInterface)Instantiate(typeof(TInterface));
        public IObject Instantiate(Type interfaceType) {
            if (!m_InstanceTypeDictionary.TryGetValue(interfaceType, out var instanceType)) {
                var record = m_SingletonBeanTypeMap.FetchRecord(interfaceType);
                record ??= m_DefaultBeanTypeMap.FetchRecord(interfaceType);
                instanceType = m_InstanceTypeDictionary[interfaceType] = record.InstanceType;
            }
            var typeMap = GetTypeMap(instanceType);
            return typeMap.Instantiate(interfaceType);
        }

        public void Dispose(object instance) {
            if (instance is IBeanInternal bean) {
                var instanceType = bean.InstanceType;
                var typeMap = GetTypeMap(instanceType);
                typeMap.Dispose(bean);
            } else {
                UnityEngine.Assertions.Assert.IsNotNull(instance);
                UnityEngine.Debug.LogError($"参数 {nameof(instance)} 的类型 {instance.GetType().FullName} 并未实现 {typeof(IBeanInternal)}");
            }
        }
    }

#if !DISABLE_DEBUGGER_ATTRIBUTE
    [DebuggerStepThrough]
    [DebuggerNonUserCode]
#endif
    internal sealed class SingletonBeanTypeMap : ITypeMap, ITypeMapInternal, IBeanTypeMapInternal {

        public event ObjectTypeMap.BeforeConstructDelegate BeforeConstruct {
            add => m_BeanTypeMap.BeforeConstruct += value;
            remove => m_BeanTypeMap.BeforeConstruct -= value;
        }

        private readonly ObjectTypeMap m_ObjectTypeMap = new();
        private readonly ObjectTypeMap m_BeanTypeMap = new();

        private readonly HashSet<ISingletonBeanInternal> m_BeanSet = new(BeanInternalEqualityComparer.Default);
        private readonly Dictionary<Type, ISingletonBeanInternal> m_BeanWithInterfaceTypeDictionary = new();
        private readonly Dictionary<Type, ISingletonBeanInternal> m_BeanWithClassTypeDictionary = new();

        public SingletonBeanTypeMap() {
            m_BeanTypeMap.BeforeConstruct += (@object, record) => {
                if (record.InstanceType == InstanceType.Singleton) {
                    var bean = (ISingletonBeanInternal)@object;
                    if (m_BeanSet.Contains(bean)) {
                        throw new InvalidOperationException($"单例 Bean {bean.GetType().FullName} 已经被实例化，不能重复实例化");
                    } else {
                        m_BeanSet.Add(bean);
                        m_BeanWithInterfaceTypeDictionary.Add(record.InterfaceType, bean);
                        m_BeanWithClassTypeDictionary.Add(record.ClassType, bean);
                    }
                }
            };
        }

        public void Clear() {
            m_BeanSet.Clear();
            m_BeanWithInterfaceTypeDictionary.Clear();
            m_BeanWithClassTypeDictionary.Clear();
            m_ObjectTypeMap.Clear();
            m_BeanTypeMap.Clear();
        }

        public void Register(Type interfaceType, Type beanType, InstanceType instanceType, bool logEnabled = true) {
            var beanRegisterAttribute = beanType.GetCustomAttribute<BeanRegisterAttribute>();
            if (beanRegisterAttribute != null) {
                m_ObjectTypeMap.Register(interfaceType, beanRegisterAttribute.ObjectType, instanceType, logEnabled);
                m_BeanTypeMap.Register(interfaceType, beanType, instanceType, logEnabled);
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Type Fetch<TInterface>() => Fetch(typeof(TInterface));
        public Type Fetch(Type interfaceType) => m_BeanTypeMap.Fetch(interfaceType);
        internal ObjectTypeMap.Record FetchRecord(Type interfaceType) => m_BeanTypeMap.FetchRecord(interfaceType, true);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public TInterface Instantiate<TInterface>() => (TInterface)Instantiate(typeof(TInterface));
        public IObject Instantiate(Type interfaceType) {
            if (m_BeanWithInterfaceTypeDictionary.TryGetValue(interfaceType, out var bean)) {
                return bean;
            }
            var record = m_BeanTypeMap.FetchRecord(interfaceType);
            if (m_BeanWithClassTypeDictionary.TryGetValue(record.ClassType, out bean)) {
                return bean;
            }
            bean = (ISingletonBeanInternal)m_BeanTypeMap.Instantiate(interfaceType);
            var @object = (IBeanObjectInternal)m_ObjectTypeMap.Instantiate(interfaceType);
            bean.Initialize(@object);
            bean.Allocate();
            return bean;
        }

        public void Dispose(object instance) {
        }
    }

#if !DISABLE_DEBUGGER_ATTRIBUTE
    [DebuggerStepThrough]
    [DebuggerNonUserCode]
#endif
    internal sealed class DefaultBeanTypeMap : ITypeMap, ITypeMapInternal, IBeanTypeMapInternal {

        public event ObjectTypeMap.BeforeConstructDelegate BeforeConstruct {
            add => m_BeanTypeMap.BeforeConstruct += value;
            remove => m_BeanTypeMap.BeforeConstruct -= value;
        }

        private readonly PooledTypeMap m_ObjectTypeMap = new();
        private readonly ObjectTypeMap m_BeanTypeMap = new();

        private readonly HashSet<IBeanInternal> m_BeanSet = new(BeanInternalEqualityComparer.Default);

        public void Clear() {
            m_BeanSet.Clear();
            m_ObjectTypeMap.Clear();
            m_BeanTypeMap.Clear();
        }

        public void Register(Type interfaceType, Type beanType, InstanceType instanceType, bool logEnabled = true) {
            var beanRegisterAttribute = beanType.GetCustomAttribute<BeanRegisterAttribute>();
            if (beanRegisterAttribute != null) {
                m_ObjectTypeMap.Register(interfaceType, beanRegisterAttribute.ObjectType, instanceType, logEnabled);
                m_BeanTypeMap.Register(interfaceType, beanType, instanceType, logEnabled);
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Type Fetch<TInterface>() => Fetch(typeof(TInterface));
        public Type Fetch(Type interfaceType) => m_BeanTypeMap.Fetch(interfaceType);
        internal ObjectTypeMap.Record FetchRecord(Type interfaceType) => m_BeanTypeMap.FetchRecord(interfaceType, false);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public TInterface Instantiate<TInterface>() => (TInterface)Instantiate(typeof(TInterface));
        public IObject Instantiate(Type interfaceType) {
            var bean = (IBeanInternal)m_BeanTypeMap.Instantiate(interfaceType);
            var @object = (IBeanObjectInternal)m_ObjectTypeMap.Instantiate(interfaceType);
            bean.Initialize(@object);
            m_BeanSet.Add(bean);
            return bean;
        }

        public void Dispose(object instance) {
            if (instance is IBeanInternal bean) {
                if (m_BeanSet.Contains(bean)) {
                    m_BeanSet.Remove(bean);
                    Dispose(bean);
                }
            } else {
                UnityEngine.Assertions.Assert.IsNotNull(instance);
                UnityEngine.Debug.LogError($"参数 {nameof(instance)} 的类型 {instance.GetType().FullName} 并未实现 {typeof(IBeanInternal)}");
            }
        }

        private void Dispose(IBeanInternal bean) {
            var @object = bean.Object;
            m_ObjectTypeMap.Dispose(@object);
            m_BeanTypeMap.Dispose(bean);
        }
    }

#if !DISABLE_DEBUGGER_ATTRIBUTE
    [DebuggerStepThrough]
    [DebuggerNonUserCode]
#endif
    internal sealed class PooledTypeMap : ITypeMap, ITypeMapInternal {

        public event ObjectTypeMap.BeforeConstructDelegate BeforeConstruct {
            add => m_Base.BeforeConstruct += value;
            remove => m_Base.BeforeConstruct -= value;
        }

        private readonly ObjectTypeMap m_Base = new();

        private readonly IDictionary<Type, Stack<IPooledObjectInternal>> m_StackWithInterfaceTypeDictionary = new Dictionary<Type, Stack<IPooledObjectInternal>>();
        private readonly IDictionary<Type, Stack<IPooledObjectInternal>> m_StackWithClassTypeDictionary = new Dictionary<Type, Stack<IPooledObjectInternal>>();

        public void Clear() {
            m_StackWithInterfaceTypeDictionary.Clear();
            m_StackWithClassTypeDictionary.Clear();
            m_Base.Clear();
        }

        public void Register(Type interfaceType, Type classType, InstanceType instanceType, bool logEnabled = true) => m_Base.Register(interfaceType, classType, instanceType, logEnabled);

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Type Fetch<TInterface>() => Fetch<TInterface>();
        public Type Fetch(Type interfaceType) => m_Base.Fetch(interfaceType);

        private Stack<IPooledObjectInternal> GetStack(Type interfaceType) {
            if (!m_StackWithInterfaceTypeDictionary.TryGetValue(interfaceType, out var stack)) {
                var classType = m_Base.Fetch(interfaceType);
                if (!m_StackWithClassTypeDictionary.TryGetValue(classType, out stack)) {
                    return m_StackWithInterfaceTypeDictionary[interfaceType] = m_StackWithClassTypeDictionary[classType] = new();
                }
                return m_StackWithInterfaceTypeDictionary[interfaceType] = stack;
            }
            return stack;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public TInterface Instantiate<TInterface>() => (TInterface)Instantiate(typeof(TInterface));
        public IObject Instantiate(Type interfaceType) {
            IPooledObjectInternal instance;
            var stack = GetStack(interfaceType);
            if (stack.Count > 0) {
                instance = stack.Pop();
            } else {
                instance = (IPooledObjectInternal)m_Base.Instantiate(interfaceType);
            }
            instance.Allocate();
            return instance;
        }

        public void Dispose(object instance) {
            if (instance is IPooledObjectInternal @object) {
                @object.Release();
                var classType = @object.GetType();
                if (m_StackWithClassTypeDictionary.TryGetValue(classType, out var stack)) {
                    stack.Push(@object);
                }
            } else {
                UnityEngine.Assertions.Assert.IsNotNull(instance);
                UnityEngine.Debug.LogError($"参数 {nameof(instance)} 的类型 {instance.GetType().FullName} 并未实现 {typeof(IPooledObjectInternal)}");
            }
        }
    }

#if !DISABLE_DEBUGGER_ATTRIBUTE
    [DebuggerStepThrough]
    [DebuggerNonUserCode]
#endif
    internal sealed class ObjectTypeMap : ITypeMap, ITypeMapInternal {

        public delegate void BeforeConstructDelegate(IObject @object, Record record);

        public event BeforeConstructDelegate BeforeConstruct;

        [DebuggerStepThrough]
        [DebuggerNonUserCode]
        public sealed class Record {

            public Type InterfaceType { get; init; }

            public Type ClassType { get; init; }

            public InstanceType InstanceType { get; init; }
        }

        private readonly Type OBJECT_TYPE = typeof(IObject);

        private readonly IDictionary<Type, Record> m_RecordDictionary = new Dictionary<Type, Record>();
        private readonly IDictionary<Type, ConstructorInfo> m_ConstructorInfoDictionary = new Dictionary<Type, ConstructorInfo>();

        public void Clear() {
            m_RecordDictionary.Clear();
            m_ConstructorInfoDictionary.Clear();
        }

        public void Register(Type interfaceType, Type classType, InstanceType instanceType, bool logEnabled = true) {
            Application.GetRuntime().AssertPlaying();

            UnityEngine.Assertions.Assert.IsNotNull(interfaceType);
            UnityEngine.Assertions.Assert.IsNotNull(classType);

            UnityEngine.Assertions.Assert.IsTrue(interfaceType.IsInterface, $"{interfaceType.FullName} 不是接口");
            UnityEngine.Assertions.Assert.IsFalse(classType.IsInterface, $"{classType.FullName} 不能是接口");
            UnityEngine.Assertions.Assert.IsFalse(classType.IsAbstract, $"{classType.FullName} 不能是抽象类");

            UnityEngine.Assertions.Assert.IsTrue(
                OBJECT_TYPE.IsAssignableFrom(classType),
                $"{classType.FullName} 需要实现 {OBJECT_TYPE.FullName}"
            );
            UnityEngine.Assertions.Assert.IsTrue(
                interfaceType.IsGenericTypeDefinition == classType.IsGenericTypeDefinition,
                $"{classType.FullName} 和 {interfaceType.FullName} 的泛型定义状态不一致"
            );

            if (interfaceType.IsGenericTypeDefinition) {
                UnityEngine.Assertions.Assert.IsTrue(
                    classType.GetInterfaces().Any(
                        @interface => @interface.IsGenericType && @interface.GetGenericTypeDefinition() == interfaceType
                    ),
                    $"{classType.FullName} 没有实现 {interfaceType.FullName}"
                );
            } else {
                UnityEngine.Assertions.Assert.IsTrue(
                    interfaceType.IsAssignableFrom(classType),
                    $"{classType.FullName} 没有实现 {interfaceType.FullName}"
                );
            }

            if (!typeof(UnityEngine.Component).IsAssignableFrom(classType)) {
                UnityEngine.Assertions.Assert.IsNotNull(
                    classType.GetConstructor(Type.EmptyTypes),
                    $"{classType.FullName} 没有公开的无参构造"
                );
            }

            Add(interfaceType, classType, instanceType, logEnabled);
        }

        private void Add(Type interfaceType, Type classType, InstanceType instanceType, bool logEnabled) {
            if (m_RecordDictionary.TryGetValue(interfaceType, out var record)) {
                if (logEnabled) {
                    UnityEngine.Debug.Log($"当前 [实现({classType.AssemblyQualifiedName})] 覆盖对 [接口({interfaceType.AssemblyQualifiedName})] 的上一个实现 [实现({record.ClassType.AssemblyQualifiedName})]");
                }
            }
            m_RecordDictionary[interfaceType] = new() {
                InterfaceType = interfaceType,
                ClassType = classType,
                InstanceType = instanceType
            };
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public TInterface Instantiate<TInterface>() => (TInterface)Instantiate(typeof(TInterface));
        public IObject Instantiate(Type interfaceType) {
            Application.GetRuntime().AssertPlaying();

            if (m_ConstructorInfoDictionary.TryGetValue(interfaceType, out var constructorInfo)) {
                return Construct(constructorInfo, m_RecordDictionary[interfaceType]);
            }
            return DoInstantiate(interfaceType);
        }

        private IObject DoInstantiate(Type interfaceType) {
            var record = FetchRecord(interfaceType);
            var classType = record.ClassType;

            UnityEngine.Assertions.Assert.IsFalse(typeof(UnityEngine.Component).IsAssignableFrom(classType), $"不允许实例化继承于{typeof(UnityEngine.Component).FullName}的组件，请{nameof(Fetch)}出实例类型后，使用Unity相关API添加组件");

            var constructorInfo = classType.GetConstructor(Type.EmptyTypes);
            m_ConstructorInfoDictionary[interfaceType] = constructorInfo;
            return Construct(constructorInfo, record);
        }

        private IObject Construct(ConstructorInfo constructorInfo, Record record) {
            UnityEngine.Assertions.Assert.IsNotNull(constructorInfo);

            var @object = (IObject)constructorInfo.Invoke(null);
            BeforeConstruct?.Invoke(@object, record);
            (@object as IObjectInternal).Construct();
            return @object;
        }

        public void Dispose(object instance) {
            if (instance is IObjectInternal @object) {
                @object.Dispose();
            } else {
                UnityEngine.Assertions.Assert.IsNotNull(instance);
                UnityEngine.Debug.LogError($"参数 {nameof(instance)} 的类型 {instance.GetType().FullName} 并未实现 {typeof(IObjectInternal)}");
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Type Fetch<TInterface>() => Fetch(typeof(TInterface));
        public Type Fetch(Type interfaceType) => FetchRecord(interfaceType).ClassType;
        internal Record FetchRecord(Type interfaceType, bool allowNotFound = false) {
            Application.GetRuntime().AssertPlaying();

            UnityEngine.Assertions.Assert.IsNotNull(interfaceType);
            UnityEngine.Assertions.Assert.IsTrue(interfaceType.IsInterface, $"{interfaceType.FullName} 不是接口");

            if (!m_RecordDictionary.TryGetValue(interfaceType, out var record)) {
                if (interfaceType.IsGenericType) {
                    var genericInterfaceType = interfaceType.GetGenericTypeDefinition();
                    if (m_RecordDictionary.TryGetValue(genericInterfaceType, out record)) {
                        var classType = record.ClassType.MakeGenericType(interfaceType.GetGenericArguments());
                        record = m_RecordDictionary[interfaceType] = new() {
                            ClassType = classType,
                            InstanceType = record.InstanceType
                        };
                    }
                }
            }
            if (!allowNotFound) {
                UnityEngine.Assertions.Assert.IsNotNull(record, $"没有找到 {interfaceType.FullName} 的实现");
            }
            return record;
        }
    }
}


namespace Internal.UIOC {

    public interface ITypeMapInternal {

        void Register(Type interfaceType, Type classType, global::UIOC.Attributes.InstanceType instanceType, bool logEnabled = true);

        void Clear();
    }

    internal interface IBeanTypeMapInternal : ITypeMap, ITypeMapInternal {
    }
}